package com.lagou.edu.factory;

import com.lagou.edu.annotation.MyAutowired;
import com.lagou.edu.annotation.MyComponent;
import com.lagou.edu.annotation.MyTransactional;
import com.lagou.edu.utils.StringUtils;
import org.springframework.beans.factory.annotation.Qualifier;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author dingxiaowei
 */
public class MyApplicationContext {

    /**
     * 自定义IoC容器
     */
    private Map<String, Object> instanceMap = new HashMap<>();

    /**
     * 声明式事务用代理对象工厂
     */
    private ProxyFactory proxyFactory;

    /**
     * 根据key取得自定义IoC容器中的实例化对象
     *
     * @param id 自定义IoC容器的key
     * @return 实例化对象
     */
    public Object getBean(String id) {
        return instanceMap.get(id);
    }

    /**
     * 创建指定包名下类的实例化对象，并放入自定义IoC容器
     *
     * @param packageName 包名
     */
    public void createBean(String packageName) throws Exception {

        // 获取指定包名下所有类的全限定名
        Set<String> classNames = getClassName(packageName, true);
        // 获取类加载器，用来加载包内类文件
        ClassLoader loader = Thread.currentThread().getContextClassLoader();

        if (classNames != null) {
            for (String className : classNames) {
                doCreateBean(loader.loadClass(className));
            }
        }
    }

    /**
     * 使用加载的类创建实例化对象
     *
     * @param clazz 用来实例化对象的类
     */
    private void doCreateBean(Class<?> clazz) throws Exception {

        Boolean hasMyComponent;
        String key;
        Object instance;

        /* 1 不实例化注解和接口，只实例化类 */
        if (clazz.isAnnotation() || clazz.isInterface()) {
            return;
        }

        /* 2 获取该类所有注解，过滤需要IoC的自定义注解的类（@MyComponent，@MyService，@MyRepository），然后创建实例化对象 */
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation : annotations) {
            hasMyComponent = false;

            /* 2.1 过滤@MyComponent */
            if (annotation.annotationType().equals(MyComponent.class)) {
                hasMyComponent = true;

            } else {
                for (Annotation subAnnotation : annotation.annotationType().getAnnotations()) {
                    // 过滤MyComponent的别名注解@MyService、@MyRepository
                    if (subAnnotation.annotationType().equals(MyComponent.class)) {
                        hasMyComponent = true;
                        break;
                    }
                }
            }

            /* 2.2 自定义注解的场合，进行实例化 */
            if (hasMyComponent) {
                /* 2.2.1 IoC容器中已经创建实例化对象的场合，退出 */
                if (hasBeanInstance(clazz)) {
                    return;
                }

                /* 2.2.2 获取自定义注解中value的值，作为IoC容器中的key */
                key = (String) annotation.getClass().getDeclaredMethod("value").invoke(annotation);
                // 如果没有设置value，就使用类名（小写首字母）
                if (StringUtils.isEmpty(key)) {
                    key = StringUtils.lowerFirst(clazz.getSimpleName());
                }

                /* 2.2.3 获取该类实例化对象，存入自定义的IoC容器 */
                instance = clazz.getDeclaredConstructor().newInstance();
                instanceMap.put(key, instance);

                /* 2.2.4 【直播视频要求】该类实现接口的场合，再将接口全限定名作为key放入容器，但实例化对象相同 */
                for (Class<?> interfaceClass : clazz.getInterfaces()) {
                    instanceMap.put(interfaceClass.getName(), instance);
                }

                break;
            }
        }
    }

    /**
     * 自动注入bean对象
     *
     * @throws IllegalAccessException
     */
    public void autowireBean() throws IllegalAccessException {
        for (Map.Entry<String, Object> map : instanceMap.entrySet()) {
            Object instance = map.getValue();
            populateBean(instance);
        }
    }

    /**
     * 维护对象属性依赖关系
     *
     * @throws IllegalAccessException
     */
    private void populateBean(Object instance) throws IllegalAccessException {

        /* 1 获取实例化对象的属性，并完成属性对象赋值 */
        Field[] fields = instance.getClass().getDeclaredFields();
        for (Field field : fields) {

            Boolean hasAutowired = false;
            Boolean hasQualifier = false;

            /* 1.1 获取该属性所有注解，过滤需要赋值的对象（@MyAutowired） */
            for (Annotation annotation : field.getAnnotations()) {
                if (annotation.annotationType().equals(MyAutowired.class)) {
                    hasAutowired = true;

                    // 判断是否需要根据名称赋值
                    if (annotation.annotationType().equals(Qualifier.class)) {
                        hasQualifier = true;
                    }

                    break;
                }
            }

            /* 1.2 需要赋值的场合，先使用key查找自定义IoC容器中的实例化对象，然后完成注入操作 */
            if (hasAutowired) {
                // 打开属性访问权限
                field.setAccessible(true);

                // 该属性已经注入过的场合，退出
                if (field.get(instance) != null) {
                    return;
                }

                String id;
                Object bean;

                /* 1.2.1 获取key */
                if (hasQualifier) {
                    // 需要按照名称查找的场合，获取名称
                    id = field.getAnnotation(Qualifier.class).value();
                } else {
                    // 以外的场合，使用类型查找
                    id = StringUtils.lowerFirst(field.getType().getSimpleName());
                }

                /* 1.2.2 根据key获取自定义IoC容器中的实例化对象 */
                bean = getBean(id);

                /* 1.2.3 【直播视频要求】如果以上步骤没有查找到（该属性为接口类型），则使用接口全限定名称查找 */
                if (bean == null) {
                    id = field.getType().getName();
                    bean = getBean(id);
                }

                /* 1.2.4 完成注入操作 */
                field.set(instance, bean);
                // 关闭属性访问权限
                field.setAccessible(false);

                /* 1.2.5 【直播视频要求】使用递归方式维护多层依赖关系 */
                populateBean(field);
            }
        }
    }

    /**
     * 创建处理事务的代理对象，然后替换自定义IoC容器中原对象
     */
    public void createTransactionProxyBean() {

        Object proxyBean;

        // 获取创建声明式事务用代理对象的工厂
        if (proxyFactory == null) {
            proxyFactory = (ProxyFactory) getBean("proxyFactory");
        }

        for (Map.Entry<String, Object> map : instanceMap.entrySet()) {
            Object instance = map.getValue();
            proxyBean = doCreateTransactionProxyBean(instance);
            // 替换自定义IoC容器中原对象
            instanceMap.put(map.getKey(), proxyBean);
        }
    }

    /**
     * 根据实例化对象创建处理事务的代理对象
     *
     * @param instance 实例化对象
     * @return 返回代理对象
     */
    private Object doCreateTransactionProxyBean(Object instance) {

        /* 1 获取声明式事务注解，没有的场合，退出 */
        Annotation myTransactional = instance.getClass().getAnnotation(MyTransactional.class);
        if (myTransactional == null) {
            return null;
        }

        /* 2【直播视频要求】该实例实现接口的场合使用jdk原生动态代理技术，否则使用cglib方式生成代理对象，最后替换原对象 */
        Class<?>[] interfaces = instance.getClass().getInterfaces();
        if (interfaces != null && interfaces.length > 0) {
            return proxyFactory.getJdkProxy(instance);
        } else {
            return proxyFactory.getCglibProxy(instance);
        }
    }

    /**
     * 检查是否已创建该类实例
     *
     * @param clazz 需要检查的类
     * @return 检查结果
     */
    private Boolean hasBeanInstance(Class<?> clazz) {
        for (Map.Entry<String, Object> map : instanceMap.entrySet()) {
            // 通过比较实例化对象类名来确定容器中是否创建过
            if (map.getValue().getClass().getName().equals(clazz.getName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取某包下所有类
     *
     * @param packageName 包名
     * @param isRecursion 是否遍历子包
     * @return 类的完整名称
     */
    private Set<String> getClassName(String packageName, boolean isRecursion) {
        Set<String> classNames = null;
        ClassLoader loader = Thread.currentThread().getContextClassLoader();

        String packagePath = packageName.replace(".", "/");

        URL url = loader.getResource(packagePath);
        if (url != null) {
            String protocol = url.getProtocol();
            if (protocol.equals("file")) {
                classNames = getClassNameFromDir(url.getPath(), packageName, isRecursion);
            } else if (protocol.equals("jar")) {
//                JarFile jarFile = null;
//                try {
//                    jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
//                } catch (Exception e) {
//                    e.printStackTrace();
//                }
//
//                if (jarFile != null) {
//                    getClassNameFromJar(jarFile.entries(), packageName, isRecursion);
//                }
            }
        } else {
            /*从所有的jar包中查找包名*/
//            classNames = getClassNameFromJars(((URLClassLoader) loader).getURLs(), packageName, isRecursion);
        }

        return classNames;
    }

    /**
     * 从项目文件获取某包下所有类
     *
     * @param filePath    文件路径
     * @param packageName 包名
     * @param isRecursion 是否遍历子包
     * @return 类的完整名称
     */
    private Set<String> getClassNameFromDir(String filePath, String packageName, boolean isRecursion) {
        Set<String> className = new HashSet<>();
        File file = new File(filePath);
        File[] files = file.listFiles();
        for (File childFile : files) {
            if (childFile.isDirectory()) {
                if (isRecursion) {
                    className.addAll(getClassNameFromDir(childFile.getPath(), packageName + "." + childFile.getName(), isRecursion));
                }
            } else {
                String fileName = childFile.getName();
                if (fileName.endsWith(".class") && !fileName.contains("$")) {
                    className.add(packageName + "." + fileName.replace(".class", ""));
                }
            }
        }

        return className;
    }

//    /**
//     * @param jarEntries
//     * @param packageName
//     * @param isRecursion
//     * @return
//     */
//    private Set<String> getClassNameFromJar(Enumeration<JarEntry> jarEntries, String packageName, boolean isRecursion) {
//        Set<String> classNames = new HashSet<String>();
//
//        while (jarEntries.hasMoreElements()) {
//            JarEntry jarEntry = jarEntries.nextElement();
//            if (!jarEntry.isDirectory()) {
//                /*
//                 * 这里是为了方便，先把"/" 转成 "." 再判断 ".class" 的做法可能会有bug
//                 * (FIXME: 先把"/" 转成 "." 再判断 ".class" 的做法可能会有bug)
//                 */
//                String entryName = jarEntry.getName().replace("/", ".");
//                if (entryName.endsWith(".class") && !entryName.contains("$") && entryName.startsWith(packageName)) {
//                    entryName = entryName.replace(".class", "");
//                    if (isRecursion) {
//                        classNames.add(entryName);
//                    } else if (!entryName.replace(packageName + ".", "").contains(".")) {
//                        classNames.add(entryName);
//                    }
//                }
//            }
//        }
//
//        return classNames;
//    }
//
//    /**
//     * 从所有jar中搜索该包，并获取该包下所有类
//     *
//     * @param urls        URL集合
//     * @param packageName 包路径
//     * @param isRecursion 是否遍历子包
//     * @return 类的完整名称
//     */
//    private Set<String> getClassNameFromJars(URL[] urls, String packageName, boolean isRecursion) {
//        Set<String> classNames = new HashSet<String>();
//
//        for (int i = 0; i < urls.length; i++) {
//            String classPath = urls[i].getPath();
//
//            //不必搜索classes文件夹
//            if (classPath.endsWith("classes/")) {
//                continue;
//            }
//
//            JarFile jarFile = null;
//            try {
//                jarFile = new JarFile(classPath.substring(classPath.indexOf("/")));
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//
//            if (jarFile != null) {
//                classNames.addAll(getClassNameFromJar(jarFile.entries(), packageName, isRecursion));
//            }
//        }
//
//        return classNames;
//    }
}
